iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Modern Web

Svelte 的奇妙冒險系列 第 21

[Svelte 的奇妙冒險] Day 21 - 表單

  • 分享至 

  • xImage
  •  

今天來介紹 SvelteKit 中內建的表單功能,主要的特點就是我們能夠利用 HTML 的 <form> submit 的行為來直接跟 SvelteKit 的 server-side 溝通。

首先要先知道一點是 method="POST"<form> submit 時是會直接發出一個 POST HTTP request 且是以 content-type:"application/x-www-form-urlencoded" 的形式,所以我們在 SvelteKit 的 server-side 的程式碼就必須要用 formData() 來解析資料。

雖然現代前端應該很少直接用原生的 <form> submit 來送出 POST request 跟後端進行溝通,但現在這樣做的好處是我們能夠在瀏覽器「禁用 javascript」的情況下依然讓網頁有一點互動。

雖然我沒遇過,但或許某些很在意流量的場景下純 HTML 的這種設計會是還可以的解決方案吧。

Default actions

首先我們要在 +page.svelte.ts 新增 actions ,這邊就可以看到我們是將把request.formData() 之後在 .get 我們要的欄位名稱。

// in src/routes/day21/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions = {
	default: async ({ request }) => {
		const data = await request.formData();
		const email = data.get('email');
		const password = data.get('password');
        console.log('day21/+page.server.ts actions.login', data.get('email'), data.get('password'));

		if (!email || !password) {
			return fail(400, { email, missing: true });
		}

		return {
			status: 200,

			success: true
		};
	}
} satisfies Actions;

然後在 +page.svelte 新增 <form>

記得設定 inputname 以及 button type="submit"

<!-- in src/routes/day21/+page.svelte -->

<div class="flex flex-col mx-auto items-center">
	<h1 class="text-primary">Login</h1>

	<form method="POST" class="w-1/2">
		<label class="form-control w-full">
			<div class="label">
				<span class="label-text">Email</span>
			</div>
			<input name="email" type="text" class="input input-bordered w-full" />
		</label>
		<label class="form-control w-full">
			<div class="label">
				<span class="label-text">Password</span>
			</div>
			<input name="password" type="password" class="input input-bordered w-full" />
		</label>
		<div class="flex flex-col">
			<button class="btn btn-primary mt-12 w-1/2 mx-auto" type="submit">Login</button>
		</div>
	</form>
</div>

接下來送出表單後就可以在 terminal 中看到所送出的資料了

然後會發現我們每次送出表單時都會重整一次畫面,沒錯這就是以前前後端合一的網站會遇到的其中一個問題,這是因為每次 <form> submit 送出 POST request 後,通常 response 會回傳要顯示的 HTML 或者 redirect 的動作。

前後端合一也是可以用 AJAX 就是了,這裡只是指出一個比較容易遇到的場景。

那如果我們故意少填了其中一個欄位讓它故意回傳 fail 呢?

就會看到 Network 中這個 Request 被回傳 400

Action 與 Page 的互動

這時如果想取得在 actions 裡面 return 的值就可以利用 $page.form 將它們取出來,例如想讓使用者得知他發生錯誤了,這時就可以用以前介紹到 directives 達成如果有 $page.form?.missing 則出現 text-error 這個 class ,並且也可以使用 value={} 讓使用者輸入的 email 刷新後依然能夠留著。

<script lang="ts">
	import { page } from '$app/stores';
</script>

<div class="flex flex-col mx-auto items-center">
	<h1 class="text-primary">Login</h1>

	<form method="POST" class="w-1/2">
		<label class="form-control w-full">
			<div class="label">
				<span class="label-text" class:text-error={!!$page.form?.missing}>Email</span>
			</div>
			<input
				name="email"
				type="text"
				class="input input-bordered w-full"
				value={$page.form?.email}
			/>
		</label>
        <label class="form-control w-full">
  			<div class="label">
  				<span class="label-text" class:text-error={!!$page.form?.missing}>Password</span>
  			</div>
  			<input name="password" type="password" class="input input-bordered w-full" />
  		</label>
          <!-- 省略其他HTML  -->

		
	</form>
</div>

Named actions

那今天如果我們想讓一個表單可以執行不同的 actions ,只要把 actions 以及 form 改為這樣

import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions = {
	login: async ({ request }) => {
		const data = await request.formData();
		const email = data.get('email');
		const password = data.get('password');
		if (!email || !password) {
			return fail(400, { email, missing: true });
		}
		console.log('day21/+page.server.ts actions.login', data.get('email'), data.get('password'));

		return {
			status: 200,
			success: true
		};
	},
	register: async ({ request }) => {
		const data = await request.formData();
		console.log('day21/+page.server.ts actions.register', data.get('email'), data.get('password'));
	}
} satisfies Actions;

<div class="flex flex-col mx-auto items-center">
	<h1 class="text-primary">Login</h1>

	<form method="POST" class="w-1/2" action="?/login">
  		<!-- 省略其他HTML  -->
		<div class="flex flex-col">
			<button class=" btn btn-primary mt-12 w-1/2 mx-auto" type="submit">Login</button>
			<button class="btn btn-secondary mt-4 w-1/2 mx-auto" type="submit" formaction="?/register">
				Register
			</button>
		</div>
	</form>
</div>

主要就是要將 form 新增 action="?/login" 來讓 button submit 時可以會直接送出 ?/login 這個 HTTP Request ,然後新增另外一個 button 並加上formaction="?/register" 也就是讓他被點擊時是送出 ?/register 這個 HTTP Request。

關掉 JS 後

講了那麼多那為什麼我們不用 onsubmit 或者 onclick 時再去打 API 就好,沒錯這就是所謂的 AJAX 而這些都是要利用 JS 才能達成的功能。

那今天這個 form 當我們關掉 JS 後它依然能正常動作

那為什麼?我們還是有用到 $page 這個 store 耶,這是因為這幾天一直有提到的一個特點因為頁面預設都會是 SSR所以 +page.svelte 在 server-side 以及 client-side 都會被執行

所以其實我們打開 Network 看失敗的 request 就能發現其時它回傳的 HTML 已經是我們要的樣子了。


參考資料

source code

https://github.com/toddLiao469469/30days-for-svelte5/tree/main/src/routes/day21

demo 站點

從今天開始會將整個 repo 部署到 cloudflare pages上,如果想直接看成品的讀者可以瀏覽下方連結

https://30days-for-svelte5.pages.dev/

之後介紹到部署時也是會使用 cloudflare ,如果有興趣的讀者可以先辦個帳號來玩玩看。


上一篇
[Svelte 的奇妙冒險] Day 20 - 錯誤處理
下一篇
[Svelte 的奇妙冒險] Day 22 - Link option
系列文
Svelte 的奇妙冒險30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言